#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"
#include "Socket.hpp"
#include <sys/epoll.h>

using namespace socket_ns;

class EpollServer
{
    const static int gnum = 64;

public:
    EpollServer(uint16_t port = 8888)
        : _port(port),
          _listensock(std::make_unique<TcpSocket>()),
          _epfd(-1)
    {
        // 1. 创建listensocket
        InterAddr addr("0", _port);
        _listensock->BuidListenSocket(addr);

        // 2. 创建epoll模型
        _epfd = ::epoll_create(128);
        if (_epfd < 0)
        {
            LOG(FATAL, "epoll_create error\n");
            exit(5);
        }

        LOG(DEBUG, "epoll_create success, epfd:%d\n", _epfd);
        // 3. 只有一个listensocket， listen sokcet关系的事件是：读事件
        // 添加listensock到epoll中
        struct epoll_event ev;
        ev.data.fd = _listensock->SockFd();
        ev.events = EPOLLIN;
        ::epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock->SockFd(), &ev);
    }
    ~EpollServer()
    {
        _listensock->Close();
        if (_epfd >= 0)
        {
            ::close(_epfd);
        }
    }

    void handlerEvent(int num)
    {
        for (int i = 0; i < num; i++)
        {
            // 那些文件描述符中的那些event事件就绪了
            uint32_t events = _evs[i].events;
            int sockfd = _evs[i].data.fd;

            // 读事件就绪,listen套接字就绪
            if (events & EPOLLIN)
            {
                // 如果是listensock，就accept
                if (sockfd == _listensock->SockFd())
                {
                    InterAddr clientaddr;
                    int newfd = _listensock->Accpeter(&clientaddr);
                    if (newfd < 0)
                        continue;

                    // 获取新连接
                    // 这里不要直接进行读取或者写入，因为不知道要不要等，而等工作是交给epoll的，所以直接添加新连接到epoll中
                    struct epoll_event ev;
                    ev.data.fd = newfd;
                    ev.events = EPOLLIN;
                    ::epoll_ctl(_epfd, EPOLL_CTL_ADD, newfd, &ev);
                    LOG(DEBUG, "_listensock ready, accept done, epoll_ctl done, newfd:%d\n", newfd);

                }
                else // 普通套接字就绪
                {
                    // 读数据
                    char buffer[1024];
                    ssize_t n = ::recv(sockfd, buffer, sizeof(buffer), 0); // 只能revc一次，不会被阻塞
                    if (n > 0)
                    {

                        LOG(DEBUG, "normal fd %d ready, recv begin...\n", sockfd);
                        buffer[n] = 0;
                        std::cout << "recv from client:" << buffer << std::endl;

                        std::string echo_string = "server echo# ";
                        echo_string += buffer;
                        ::send(sockfd, echo_string.c_str(), echo_string.size() + 1, 0);
                    }
                    else if (n == 0)
                    {
                        // 客户端关闭
                        // 从epoll中删除
                        ::epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, NULL); // 要删除的fd，必须是合法的
                        LOG(DEBUG, "normal fd %d close\n", sockfd);
                        ::close(sockfd);
                    }
                    else
                    {
                        ::epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, NULL); // 要删除的fd，必须是合法的
                        LOG(DEBUG, "client close\n");
                        ::close(sockfd);
                    }
                }
            }
            // 写事件就绪
        }
    }

    void Loop()
    {
        // timeout = 0 代表非阻塞
        // timeout = -1 代表一直等待,阻塞状态
        int timeout = -1;
        while (true)
        {
            int nfds = ::epoll_wait(_epfd, _evs, gnum, timeout);
            switch (nfds)
            {
            case 0:
                LOG(DEBUG, "epoll_wait timeout...\n");
                break;
            case -1:
                LOG(DEBUG, "epoll_wait failed...\n");
                break;
            default:
                LOG(DEBUG, "epoll_wait have event ready, nfds:%d\n", nfds);
                // 处理事件
                handlerEvent(nfds);
                break;
            }
        }
    }

private:
    uint16_t _port;
    std::unique_ptr<Socket> _listensock;
    int _epfd;
    struct epoll_event _evs[gnum]; // 将就绪队列的信息放到数组中，每次调用epoll_wait时，都会将数组清空，然后返回就绪的个数
};